#include "d3d_obj.h"
#include <d3dx9.h>

using namespace dexgel;
using namespace dexgel::priv;

// Declare our static variables
IDirect3D9* RendererDx::mInterface = NULL;
IDirect3DDevice9* RendererDx::mDevice = NULL;
HRESULT RendererDx::mResult = 0;


VOID SetupMatrices()
{
	// For our world matrix, we will just rotate the object about the y-axis.
	D3DXMATRIXA16 matWorld;

	// Set up the rotation matrix to generate 1 full rotation (2*PI radians) 
	// every 1000 ms. To avoid the loss of precision inherent in very high 
	// floating point numbers, the system time is modulated by the rotation 
	// period before conversion to a radian angle.
	//UINT iTime = timeGetTime() % 1000;
	FLOAT fAngle = 0.f; //iTime * ( 2.0f * D3DX_PI ) / 1000.0f;
	D3DXMatrixRotationY( &matWorld, fAngle );
	D3DXMatrixTranslation( &matWorld, 0.0f, 0.0f, 0.0f );
	RendererDx::mDevice->SetTransform( D3DTS_WORLD, &matWorld );

	// Set up our view matrix. A view matrix can be defined given an eye point,
	// a point to lookat, and a direction for which way is up. Here, we set the
	// eye five units back along the z-axis and up three units, look at the
	// origin, and define "up" to be in the y-direction.
	D3DXVECTOR3 vEyePt( 0.0f, 0.5f,-5.0f );
	D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
	D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
	D3DXMATRIXA16 matView;
	D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
	RendererDx::mDevice->SetTransform( D3DTS_VIEW, &matView );

	// For the projection matrix, we set up a perspective transform (which
	// transforms geometry from 3D view space to 2D viewport space, with
	// a perspective divide making objects smaller in the distance). To build
	// a perpsective transform, we need the field of view (1/4 pi is common),
	// the aspect ratio, and the near and far clipping planes (which define at
	// what distances geometry should be no longer be rendered).
	D3DXMATRIXA16 matProj;
	D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f );
	RendererDx::mDevice->SetTransform( D3DTS_PROJECTION, &matProj );
}

// Initializes our D3D object -- Returns true on success, false otherwise
bool RendererDx::init(HWND hwnd)
{
	// Create the D3D object, which is needed to create the D3DDevice.
	mInterface = Direct3DCreate9(D3D_SDK_VERSION);

	// Error Check
	if(mInterface == NULL)
		return false;

	D3DPRESENT_PARAMETERS params = {0}; 
	params.Windowed = TRUE; 
	params.BackBufferCount = 1;
	params.SwapEffect = D3DSWAPEFFECT_FLIP;
	params.BackBufferFormat = D3DFMT_UNKNOWN;
	

	mResult = mInterface->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
									   D3DCREATE_HARDWARE_VERTEXPROCESSING,
									   &params, &mDevice);

	// It's possible we'll get an error because not all vid-cards can handle vertex processing
	// So in the event we do get an error we'll try to make the device again.  We will
	// only change D3DCREATE_HARDWARE_VERTEXPROCESSING to D3DCREATE_SOFTWARE_VERTEXPROCESSING
	// which says, "Do the vertex processing in software" 
	if(mResult != D3D_OK)
	{
		mResult = mInterface->CreateDevice(D3DADAPTER_DEFAULT,
										   D3DDEVTYPE_HAL,
										   hwnd,
										   D3DCREATE_SOFTWARE_VERTEXPROCESSING,
										   &params,
										   &mDevice);
		if(mResult != D3D_OK)
			return false; // Couldn't create a D3D 9.0 device
	}						
    return true; // We got loaded
}

// Free up all the memory
void RendererDx::deinit()
{
	if(mDevice != NULL)
		mDevice->Release();

	if(mInterface != NULL)
		mInterface->Release();

	// Zero out our D3D interface and device 
	mDevice = NULL;
	mInterface = NULL;
}


// Begins the scene
void RendererDx::begin()
{
	// This begins our scene.
	assert( 
			SUCCEEDED( mDevice->BeginScene() )
			);

	SetupMatrices();
}

// End the scene and draw it
void RendererDx::end()
{
	// This ends the scene
	assert( SUCCEEDED(
		 mDevice->EndScene() 
		 ));

	// This presents (draws) the next back buffer (we only have one in our case) to the screen
	// By parameter:
	// NULL -- Must be NULL unless a swap chain was created with D3DSWAPEFFECT_COPY
	//		   **NOTE** We used D3DSWAPEFFECT_DISCARD
	// NULL -- Must be NULL unless a swap chain was created with D3DSWAPEFFECT_COPY
	// NULL -- HWND whose client area is taken as the target for this presentation.  If this is
	//		   NULL then the HWND set in the D3DPRESENT_PARAMETERS (set in our init() function)
	//		   is used
	// NULL -- Must be NULL unless a swap chain was created with D3DSWAPEFFECT_COPY 
	assert(  SUCCEEDED(
		mDevice->Present(NULL, NULL, NULL, NULL)
		));
}

// This renders a list of SVertex type vertices that is "numVerts" long
bool RendererDx::render(int primitiveType, SVertex *vertList, int numVerts)
{
	// The first thing we have to do before rendering is tell D3D what
	// type of vertices we'll be rendering.  We do this by passing the
	// #define that stipulates our vertex type.
	mDevice->SetFVF(SVertexType);
	
	// Now we are ready to render.  We can do this with one function call.
	// You'll notice the "UP" in the function name.  This stands for "user pointer" and 
	// means that we are passing D3D a pointer to a list of vertices to be rendered.  
	// Optionally we could use a D3D vertex buffer, this is a more specialized class that
	// increases rendering speeds.

	// This function takes vertex arrays and index arrays (declared in typical C++ fashion)
	// and renders them to the screen.  By parameter:
	// D3DPT_TRIANGLELIST -- The type of primitive to render
	// numVerts / 3 -- Number of primitives (in our case triangles) to render
	// iList -- Pointer to index array
	// vertList -- Pointer to our vertex array
	// vList -- Point to the vertex array
	// sizeof(SVertex) -- Size of an individual vertex
	D3DPRIMITIVETYPE dxPrimitive = D3DPT_TRIANGLELIST;
	if (primitiveType == LINES ) dxPrimitive = D3DPT_LINESTRIP;
	//if (primitiveType == TRIS ) dxPrimitive = D3DPT_TRIANGLELIST;

	assert(  SUCCEEDED(
		mResult = mDevice->DrawPrimitiveUP(dxPrimitive, numVerts, vertList, sizeof(SVertex))
	));
	
	return (mResult == D3D_OK); // Returns status of DrawPrimitiveUP()
}

// Clears the viewport to a specified ARGB color
bool RendererDx::clearColor(int color)
{
	mResult = mDevice->Clear(0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0);
		return (mResult == D3D_OK);
}


void RendererDx::SetTransform( float* matWorld )
{
	D3DXMATRIXA16 *m = (D3DXMATRIXA16*)matWorld;
	mResult = mDevice->SetTransform( D3DTS_WORLD, m );
	assert(mResult == D3D_OK);
}
